package de.akabahn.ebd.control.comm;

import java.io.IOException;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import de.akabahn.ebd.comm.ConnectionObserver;
import de.akabahn.ebd.comm.NetworkException;
import de.akabahn.ebd.comm.ObservableConnection;

/**
 * Class that manages the connection to a WiFi network using the
 * <code>WifiManager</code> provided by the android framework. Extends from
 * <code>ObservableConnection</code> so any class that is interested in the
 * current connection status and in the change of this status, may register
 * itself as an observer so it gets notified on a connection state change.
 * 
 * @author Peter Eimann
 * @since 0.1
 * @version 0.2
 */
public class NetworkConnection extends ObservableConnection {

	private static NetworkConnection instance;
	private WifiManager wifiManager;
	private String expectedSSID;

	private ExecutorService executor;

	/**
	 * Create a new instance and start the connection observer thread that
	 * monitors the connection status changes.
	 * 
	 * @param context
	 *            The android application context
	 * @param expectedSSID
	 *            The expected SSID of the WiFi network, only a connection to
	 *            this SSID is accepted as a valid connection
	 * @throws IllegalArgumentException
	 *             thrown if the input parameters are invalid
	 * @see ConnectionObserver
	 */
	private NetworkConnection(Context context, String expectedSSID) {
		if (expectedSSID == null) {
			throw new IllegalArgumentException("expectedSSID must not be null");
		}

		wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
		this.expectedSSID = expectedSSID;

		executor = Executors.newSingleThreadExecutor();
		executor.execute(new ConnectionObserver(this));
	}

	/**
	 * Return the singleton instance of this class
	 * 
	 * @param context
	 *            The android application context
	 * @param expectedSSID
	 *            The expected SSID of the WiFi network, only a connection to
	 *            this SSID is treated as a valid connection
	 * @return The singleton instance
	 */
	public static NetworkConnection getInstance(Context context, String expectedSSID) {
		if (instance == null) {
			instance = new NetworkConnection(context, expectedSSID);
		}

		return instance;
	}

	/**
	 * Reconnect to the current WiFi network by calling
	 * <code>reassociate()</code> on the <code>WiFiManager</code>.
	 */
	@Override
	public void reconnect() {
		wifiManager.reassociate();
	}

	/**
	 * Check if there's a current connection to a WiFi network and if the SSID
	 * of this network is equal to the expeced SSID.
	 * 
	 * @return <code>true</code> if a connection to the expected WiFi is
	 *         established
	 */
	@Override
	public boolean isConnected() {
		WifiInfo connInfo = wifiManager.getConnectionInfo();

		if (connInfo == null) {
			return false;
		} else {
			String connSSID = connInfo.getSSID();
			if ((connSSID != null) && (connSSID.equals(expectedSSID))) {
				return true;
			} else {
				return false;
			}
		}
	}

	/**
	 * Return the SSID of the connected WiFi network.
	 * 
	 * @return The SSID (may be <code>null</code> if there's no connection
	 *         established)
	 */
	public String getConnectionSSID() {
		WifiInfo connInfo = wifiManager.getConnectionInfo();

		if (connInfo != null) {
			return connInfo.getSSID();
		} else {
			return null;
		}
	}

	/**
	 * Return the expected SSID
	 * 
	 * @return The expected SSID
	 */
	public String getExpectedSSID() {
		return expectedSSID;
	}

	/**
	 * Perform a <code>ping</code> to the passed <code>server</code> to check
	 * whether this server is available or not.
	 * 
	 * @param server
	 *            The server to ping
	 * @return <code>true</code> if the server is available (the ping succeeded)
	 * @throws NetworkException
	 * @throws IllegalArgumentException
	 *             thrown if the input parameters are invalid
	 */
	public boolean ping(String server) throws NetworkException {
		if ((server == null) || ("".equals(server.trim()))) {
			throw new IllegalArgumentException("server must not be null or empty");
		}

		try {
			if (InetAddress.getByName(server).isReachable(3000)) {
				return true;
			} else {
				return false;
			}
		} catch (IOException e) {
			throw new NetworkException("Ping failed", e);
		}
	}
}
